import {Injectable} from '@nestjs/common';
import {exec} from "child_process";
import * as fs from "fs";
import {FileEntity} from "./file.entity";
import {ValidateException} from "../../excepiton/ValidateException";
import * as Path from "path";
import {TagService} from "../tag/tag.service";
import {pathJoin} from "../../util/common";

@Injectable()
export class FileService {

    constructor(
        private tagService: TagService
    ) {
    }

    async fetch(path, isDirectory = false): Promise<FileEntity[]> {
        if (path) {
            const readdirSync = fs.readdirSync(`${path}/`);
            let results: FileEntity[] = [];
            readdirSync.forEach(fileName => {
                try {
                    const filePath = `${path}/${fileName}`;
                    const fileEntity = FileService.ofFileEntity(filePath);
                    this.tagService.fillTagIds(fileEntity)
                    results.push(fileEntity);
                } catch (e) {
                    console.error(`错误`);
                }
            })
            if (isDirectory) {
                results = results.filter(value => value.isDirectory)
            }
            return results;
        }
        return await this.readLogicalDisk()
    }

    public static ofFileEntity(filePath: string) {
        const split = filePath.split("/");
        const fileName = split[split.length - 1]
        const stats = fs.lstatSync(filePath);
        const isDirectory = stats.isDirectory();
        const fileEntity = new FileEntity(fileName, filePath, isDirectory, false);
        fileEntity.size = stats.size;
        fileEntity.createTime = stats.birthtime;
        fileEntity.lastUpdateTime = stats.mtime;
        return fileEntity;
    }

    private async readLogicalDisk() {
        const r = await FileService.execute(`wmic logicaldisk get caption`);
        const filter = (<any>r).split(/\r\r\n/).filter(Boolean).map(i => i.trim());
        filter.splice(0, 1);
        return filter.map(fileName => new FileEntity(fileName, fileName, false, true));
    }

    static async execute(command: string) {
        return new Promise((resolve, reject) => {
            exec(command, (error, stdout, stderr) => {
                if (error || stderr) {
                    if (error.code === 1) {
                        resolve(stdout)
                    } else {
                        console.error(error);
                        reject({error})
                    }
                } else {
                    resolve(stdout);
                }
            });
        });
    }

    async open(path: string) {
        await FileService.execute(`explorer file://${path}`)
    }

    async move(file: FileEntity, targetPath: string) {
        FileService.validateDir(targetPath)
        const newPath = await this.getNewPath(targetPath, file);
        if (file.isFile) {
            fs.renameSync(file.filePath, newPath);
        } else if (file.isDirectory) {
            fs.mkdirSync(newPath);
            const fileEntities = await this.fetch(file.filePath);
            for (let fileEntity of fileEntities) {
                await this.move(fileEntity, newPath)
            }
            fs.rmdirSync(file.filePath)
        }
        TagService.renameFilePath(file.filePath, newPath)
    }

    private async getNewPath(targetPath: string, file: FileEntity, isMove = true) {
        if (isMove) {
            return pathJoin(targetPath, file.fileName)
        } else {
            const regExp = new RegExp(`${file.fileName}\\([1-9]+\\)`);
            const fileEntities = await this.fetch(targetPath);
            const sameNameCount = fileEntities.filter(value => value.fileName === file.fileName || regExp.test(value.fileName)).length;
            return pathJoin(targetPath, `${file.fileName}${sameNameCount > 0 ? `(${sameNameCount})` : ''}`);
        }
    }

    async rename(file: FileEntity, newName: string) {
        const parentFolderPath = FileEntity.getParentFolderPath(file);
        const fileEntities = await this.fetch(parentFolderPath);
        const isExit = fileEntities.some(value => value.fileName === newName && value.filePath !== file.filePath);
        if (isExit) {
            throw new ValidateException(`新文件名已存在`)
        }
        const newPath = file.filePath.replace(file.fileName, newName);
        fs.renameSync(file.filePath, newPath);
        TagService.renameFilePath(file.filePath, newPath)
    }

    async copy(file: FileEntity, targetPath: string) {
        FileService.validateDir(targetPath);
        const newPath = await this.getNewPath(targetPath, file, false);
        if (file.isFile) {
            fs.copyFileSync(file.filePath, newPath)
        } else if (file.isDirectory) {
            fs.mkdirSync(newPath);
            const fileEntities = await this.fetch(file.filePath);
            for (let fileEntity of fileEntities) {
                await this.copy(fileEntity, newPath)
            }
        }
        TagService.copyFilePath(file.filePath, newPath)

    }

    private static validateDir(targetPath: string) {
        if (!fs.lstatSync(targetPath).isDirectory()) {
            throw new ValidateException(`目标路径不是文件夹`);
        }
    }

    async remove(file: FileEntity) {
        if (file.isFile) {
            // await trash(file.filePath)
            fs.rmSync(file.filePath)
        } else if (file.isDirectory) {
            const fileEntities = await this.fetch(file.filePath);
            for (let fileEntity of fileEntities) {
                await this.remove(fileEntity);
            }
            fs.rmdirSync(file.filePath)
        }
        TagService.deleteFilePath(file.filePath)
    }

    async search(path: string, search: string) {
        /**
         * todo 有空多线程试试
         */
        let result = []
        let fileEntities = await this.fetch(path);
        if (!search) {
            result = fileEntities
        } else {
            result.push(...fileEntities.filter(value => value.fileName.toLowerCase().includes(search.toLowerCase())));
        }
        // for (let fileEntity of fileEntities) {
        //     if (!fileEntity.isFile) {
        //         result.push(...await this.search(fileEntity.filePath, search));
        //     }
        // }
        return result;
    }
}
